#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <assert.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/dir.h>
#include <sys/stat.h>

#include "bstrlib.h"
#include "server.h"
#include "common.h"
#include "receive.h"
#include "send_file_listing.h"
#include "send_metadata.h"
#include "send_slice.h"
#include "fd_list.h"

void send_slice (fd_list list, int i){
    char *msg = NULL, *dir = NULL, *aux_buf = NULL,\
         *str_aux = NULL, *aux = NULL;
    int aux_send=0, fd_client=0, offset=0, amount=0, trans=0;
    long readed;
    FILE *f = NULL;
    struct stat *file_prop = NULL;

    /*Obtenemos nombre del archivo*/
    str_aux = fd_list_get_filename(list, i);
    fd_client = fd_list_get_fd(list, i);
    offset = fd_list_get_offset(list, i);
    amount = fd_list_get_amount(list, i);
    trans = fd_list_get_transferred(list, i);


    /*Vemos que el archivo está disponible, en caso contrario
    enviamos mensaje 202*/
    file_prop = calloc(1, sizeof(struct stat));
    if (file_prop == NULL){
        perror("calloc-file_prop");
        free(str_aux);
        str_aux = NULL;
        send_msg(fd_client, MSG199);
        close(fd_client);
        fd_list_remove(list, i);
        return;
    }
    dir = calloc(strlen(FOLDER)+strlen(str_aux)+2,sizeof(char *));
    if (dir == NULL){
        perror("calloc-dir");
        free(file_prop);
        file_prop = NULL;
        free(str_aux);
        str_aux = NULL;
        send_msg(fd_client, MSG199);
        close(fd_client);
        fd_list_remove(list, i);
        return;
    }

    strcpy(dir, FOLDER);
    strncat(dir, str_aux, strlen(str_aux));
    if (stat (dir, file_prop) < 0){
        perror("stat");
        if (errno == 29){
            if (send_msg(fd_client, MSG202) == -1){
                fd_list_remove(list, i);
                free(file_prop);
                file_prop = NULL;
                free(str_aux);
                str_aux = NULL;
                free(dir);
                dir = NULL;
                return;
            }
        }
    }
    /*Evaluamos que el offset no se encuentre "afuera" del archivo y
    que la cantidad de bytes pedidos no supere el tamaño del archivo*/
    else if ((offset > file_prop->st_size)||\
             (offset + amount > file_prop->st_size)\
             ){
        if (send_msg(fd_client, MSG203)==-1){
            fd_list_remove(list, i);
            free(file_prop);
            file_prop = NULL;
            free(str_aux);
            str_aux = NULL;
            free(dir);
            dir = NULL;
            return;
        }

    }
    /*si llegamos al else es porque el archivo existe y se nos pidio una
    cantidad razonable de bytes*/
   else {
        if (trans == 0){
            if (send_msg(fd_client, MSG0)==-1){
                fd_list_remove(list, i);
                free(file_prop);
                file_prop = NULL;
                free(str_aux);
                str_aux = NULL;
                free(dir);
                dir = NULL;
                return;
            }
        }
        aux = calloc (strlen(FOLDER) + strlen(str_aux)+2, sizeof(char*));
        if (aux == NULL){
            perror("calloc-aux");
            free(file_prop);
            file_prop = NULL;
            free(dir);
            dir = NULL;
            free(str_aux);
            str_aux = NULL;
            send_msg(fd_client, MSG199);
            close(fd_client);
            fd_list_remove(list, i);
            return;
        }
        strcpy (aux,FOLDER);
        strncat (aux, str_aux, strlen(str_aux));
        /*Abrimos el archivo*/
        if ((f = fopen (aux,"rb")) == NULL) {
            /*en caso de error liberamos todo y retornamos*/
            perror ("fopen");
            free(dir);
            dir=NULL;
            free(file_prop);
            file_prop = NULL;
            free(aux);
            aux = NULL;
            free(str_aux);
            str_aux = NULL;
            send_msg(fd_client, MSG199);
            close(fd_client);
            fd_list_remove(list, i);
            return;
        }
        /*Nos posicionamos en la ubicacion "offset+trans" en el archivo*/
        fseek (f,offset+trans, SEEK_SET);

        if (trans < amount){
            /*Vamos a utilizar aux_buf para ir guardando lo que vamos leyendo
            del archivo*/
            aux_buf = calloc (READ_SIZE+2, sizeof(char*));
            if (aux_buf == NULL){
                perror("calloc-aux_buf");
                free(dir);
                dir=NULL;
                free(file_prop);
                file_prop = NULL;
                free(aux);
                aux = NULL;
                free(str_aux);
                str_aux = NULL;
                send_msg(fd_client, MSG199);
                close(fd_client);
                fd_list_remove(list, i);
                /*Cerramos el archivo*/
                fclose (f);
                return;
            }
            /*Si la cantidad de datos a leer es mayor que el tamaño maximo
            de lectura, pedimos el read con la variable READ_SIZE, caso
            contrario leemos lo que necesitamos*/
            if (amount-trans > READ_SIZE){
                readed = fread(aux_buf, 1, READ_SIZE, f);
            }
            else{
                readed = fread(aux_buf, 1, amount - trans, f);
            }
            /*Actualizamos en la estructura cuanto llevamos enviado, y aca
            lo actualizamos con el valor de readed, que es lo que ha leido*/
            fd_list_set_transferred(list, i, trans+readed);
            /*alojamos memoria para el mensaje a enviar al cliente y cargamos
            el contenido del mismo*/
            msg = calloc(readed+sizeof(READ_SIZE)+5, sizeof(char*));
            if (msg == NULL){
                perror("calloc-msg");
                free(dir);
                dir=NULL;
                free(file_prop);
                file_prop = NULL;
                free(aux);
                aux = NULL;
                free(aux_buf);
                aux_buf = NULL;
                free(str_aux);
                str_aux = NULL;
                send_msg(fd_client, MSG199);
                close(fd_client);
                fd_list_remove(list, i);
                /*Cerramos el archivo*/
                fclose (f);
                return;
            }
            /*primero el numero de bytes leidos*/
            sprintf(msg, "%li ",readed);
            if (send_msg(fd_client, msg)==-1){
                fd_list_remove(list, i);
                free(msg);
                msg=NULL;
                free(aux_buf);
                aux_buf = NULL;
                free(aux);
                aux = NULL;
                /*Cerramos el archivo*/
                fclose (f);
                free(dir);
                dir=NULL;
                free(file_prop);
                file_prop = NULL;
                free(str_aux);
                str_aux = NULL;
                return;
            }
            /*luego metemos los datos leidos, y usamos memmove dado que no
            se pueden usar funciones de string por si leemos archivos
            que no sean de texto plano*/
            memcpy(msg, aux_buf, readed);
            /*en lugar de usar send_msg usamos la funcion send directamente
            dado que son necesarios pasarle otros parametros*/
            aux_send = send(fd_client, msg, readed, 0);
            if (aux_send == -1){
                perror("send");
                if (errno==ECONNRESET || errno==EPIPE || errno==ESPIPE){
                    free(msg);
                    msg=NULL;
                    free(aux_buf);
                    aux_buf = NULL;
                    free(aux);
                    aux = NULL;
                    /*Cerramos el archivo*/
                    fclose (f);
                    free(dir);
                    dir=NULL;
                    free(file_prop);
                    file_prop = NULL;
                    free(str_aux);
                    str_aux = NULL;
                    fd_list_remove(list, i);
                    return;
                }
            }
            /*Enviamos el terminador*/
            if(send_msg(fd_client, "\r\n")==-1){
                free(msg);
                msg=NULL;
                free(aux_buf);
                aux_buf = NULL;
                free(aux);
                aux = NULL;
                /*Cerramos el archivo*/
                fclose (f);
                free(dir);
                dir=NULL;
                free(file_prop);
                file_prop = NULL;
                free(str_aux);
                str_aux = NULL;
                fd_list_remove(list, i);
                return;
            }
            free(msg);
            msg=NULL;
            free(aux_buf);
            aux_buf = NULL;
            free(aux);
            aux = NULL;
            /*Cerramos el archivo*/
            fclose (f);
            free(dir);
            dir=NULL;
            free(file_prop);
            file_prop = NULL;
            free(str_aux);
            str_aux = NULL;
            return;
        }
        /*Como ultima linea enviamos 0 bytes y el terminador*/
        if(send_msg(fd_client, "0 \r\n")==-1){
            free(aux);
            aux = NULL;
            /*Cerramos el archivo*/
            fclose (f);
            free(dir);
            dir=NULL;
            free(aux);
            aux = NULL;
            free(file_prop);
            file_prop = NULL;
            free(str_aux);
            str_aux = NULL;

            fd_list_remove(list, i);
            return;
            }
        free(aux);
        aux = NULL;
        /*Cerramos el archivo*/
        fclose (f);
    }
    free(dir);
    dir=NULL;
    free(aux);
    aux = NULL;
    free(file_prop);
    file_prop = NULL;
    free(str_aux);
    str_aux = NULL;

    fd_list_set_finish(list, i, true);
    return;
}
